    <!DOCTYPE html>
<html lang="zh-cn">
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<meta name="author" content="nodejh">
		<meta name="description" content="My Blog">
		<meta name="generator" content="Hugo 0.31.1" />
		<title>检测 Express 路由中的参数合法性 &middot; nodejh</title>
		<link rel="shortcut icon" href="http://nodejh.com/images/favicon.ico">
		<link rel="stylesheet" href="http://nodejh.com/css/style.css">
		<link rel="stylesheet" href="http://nodejh.com/css/highlight.css">
		

		
		<link rel="stylesheet" href="http://nodejh.com/css/font-awesome.min.css">
		

		
		<link href="http://nodejh.com/index.xml" rel="alternate" type="application/rss+xml" title="nodejh" />
		
	</head>

    <body>
       <nav class="main-nav">
	
	
		<a href='http://nodejh.com/'> <span class="arrow">←</span>Home</a>
	
	<a href='http://nodejh.com/post'>Archive</a>
	<a href='http://nodejh.com/tags'>Tags</a>
	<a href='http://nodejh.com/projects'>Projects</a>
	<a href='http://nodejh.com/about'>About</a>

	

	
	<a class="cta" href="http://nodejh.com/index.xml">Subscribe</a>
	
</nav>


        <section id="wrapper" class="post">
            <article>
                <header>
                    <h1>
                        检测 Express 路由中的参数合法性
                    </h1>
                    <h2 class="headline">
                    Oct 15, 2016 06:46
                    · 2004 words
                    · 4 minutes read
                      <span class="tags">
                      
                      
                          
                              <a href="http://nodejh.com/tags/node.js">Node.js</a>
                          
                              <a href="http://nodejh.com/tags/javascript">JavaScript</a>
                          
                              <a href="http://nodejh.com/tags/express">Express</a>
                          
                      
                      
                      </span>
                    </h2>
                </header>
                
                  
                    <div id="toc">
                      <nav id="TableOfContents">
<ul>
<li>
<ul>
<li>
<ul>
<li>
<ul>
<li><a href="#函数详解">函数详解</a></li>
<li><a href="#模块使用">模块使用</a></li>
</ul></li>
</ul></li>
</ul></li>
</ul>
</nav>
                    </div>
                  
                
                <section id="post-body">
                    <p>本文以 Express 框架为基础，讲诉如何通过一个中间件来检测 Express 路由中传输的参数是否合法。</p>

<p></p>

<p>几乎对于任何应用，前后端都需要进行传输数据。不管是通过 HTTP 请求的 POST 方法还是 GET 方法，数据校验都是必要的操作。</p>

<p>对于大部分 API 来说，可能只需要判断传入的参数是否为 undefined 或 null，所以这个时候，为了减少重复代码，我们可以写一个简单的中间件来处理路由中的参数。</p>

<p>这个中间件的需求如下：</p>

<ul>
<li>检测路由中的一般参数是否为 undefined、null、[]、&rdquo;</li>
<li>中间件同时还需要能对特殊参数做处理，如一个参数值在 1-100 之间</li>
</ul>

<p>最终写出来的处理参数的模块：</p>

<pre><code>/**
 * 检测路由中的参数
 * @method checkParameters
 * @param  {object}        req     http请求
 * @param  {object}        res     http响应
 * @param  {Function}      next
 * @param  {[type]}        special 特殊参数，
 *                                 格式： [{eval: 'req.query.id&gt;0', key:'id', type: 'query'}]
 *                                 key 属性的值必须和eval中对应，且type只能是 query/params/body
 * @return {object}        若参数错误，直接返回json到前端；正确，则next()
 */
function checkParameters(req, res, next, special) {
  // console.log('params: ', req.params);
  // console.log('body: ', req.body);
  // console.log('query: ', req.query);
  console.log('specialParams: ', typeof special);
  // 判断是否传入第四个参数
  if (Array.isArray(special)) {
    // 判断特殊参数
    for (let i = 0; i &lt; special.length; i++) {
      // console.log('special[i]: ', special[i]);
      // console.log('special[i] eval: ', special[i].hasOwnProperty('eval'));
      // console.log('special[i] key: ', special[i].hasOwnProperty('key'));
      // console.log('special[i] type: ', special[i].hasOwnProperty('type'));

      // 判断是否具有 eval, key, type 三个参数
      if (! (special[i].hasOwnProperty('eval')
        &amp;&amp; special[i].hasOwnProperty('key')
        &amp;&amp; special[i].hasOwnProperty('type'))) {
        return res.json({
          code: 90001,
          msg: '检测参数属否正确时，传入的特殊参数格式不完全',
          detail: special
        });
      }
      // 判断 eval, key, type 三个参数是否为 undefined
      if ( !(typeof special[i]['eval'] !== undefined
      &amp;&amp; typeof special[i]['key'] !== undefined
      &amp;&amp; typeof special[i]['type'] !== undefined)) {
        return res.json({
          code: 90002,
          msg: '检测参数属否正确时，传入的特殊参数为 undefined',
          detail: special
        });
      }
      const evalString = special[i]['eval'];
      const type = special[i]['type'];
      const key = special[i]['key'];
      // 判断 key 和 eval 是否匹配
      console.log('length: ', evalString
      .split('req.' + type + '.' + key)
      .length);
      const length = evalString.split('req.' + type + '.' + key).length;
      if (length &lt; 1) {
        return res.json({
          code: 90003,
          msg: '检测参数属否正确时，传入的特殊参数为格式不正确',
          detail: special
        });
      }
      // 执行 eval
      if (!eval(evalString)) {
        return res.json({
          code: 90004,
          msg: '检测参数属否正确时，参数不匹配传入的条件 ' + evalString,
          detail: special,
          parameters: req[type]
        });
      }
      // 从普通参数中删除特殊参数
      // console.log('delete: ', req[type][key]);
      delete req[type][key];
    }
  }

  const params = req.params;
  // 去掉 req.params 中 {'0': '', ...} 这一项
  delete params['0'];
  // console.log('params: ', params);
  const body = req.body;
  const query = req.query;
  const common = Object.assign(params, body, query);
  console.log('common: ', common);
  // 检测参数属否为undefined
  for (let i in common) {
    if (typeof common[i] === 'undefined') {
      return res.json({
        code: 90005,
        msg: '检测参数属否正确时，' + i + ' 参数为undefined',
        detail: common
      });
    }
    // 检测参数属否为null
    if (common[i] === null) {
      return res.json({
        code: 90006,
        msg: '检测参数属否正确时，' + i + ' 参数为null',
        detail: common
      });
    }
    // 检测参数是否为空字符串 ''
    if (common[i] === '') {
      return res.json({
        code: 90007,
        msg: '检测参数属否正确时，' + i + ' 参数为空字符串',
        detail: common
      });
    }
    // 检测参数是否为空数组 []
    if (common[i] === '') {
      return res.json({
        code: 90008,
        msg: '检测参数属否正确时，' + i + ' 参数为空数组',
        detail: common
      });
    }
  }

  return next();
}

module.exports = checkParameters;

</code></pre>

<h4 id="函数详解">函数详解</h4>

<p><strong>1. 特殊参数的处理</strong></p>

<p>这个模块，首先对特殊参数做处理。<code>special</code> 这个参数是一个数组，格式如下：</p>

<pre><code>[{
  eval: 'parseInt(req.query.id) &gt; 1 &amp;&amp; parseInt(req.query.id) &lt; 10',
  key: 'id',
  type: 'query'
}, {
  eval: 'parseInt(req.query.age) === 11',
  key: 'age',
  type: 'query'
}]
</code></pre>

<p>key 属性的值必须和eval中对应，且type只能是 query/params/body。</p>

<ul>
<li>首先判断函数是否传入第四个参数，并且第四个参数是否为数组，如果这两个条件成立，说明有特殊参数需要处理。</li>
<li>然后判断是否具有 eval, key, type 三个参数。eval 参数是一个参数条件表达式，如 &lsquo;id&gt;0&rsquo;，最终通过 <code>eval()</code> 函数来执行这个表达式。<code>key</code> 表示参数属性名，type 表示是通过何种方式传递的参数，如 query/params/body。</li>
<li>接下来判断  eval, key, type 三个参数自身是否为 undefined 或 null</li>
<li>再然后判断 判断 key 和 eval 是否匹配。key 和 eval 的匹配，主要是为了后面从一般参数中删除特殊参数</li>
<li>再执行 eval 表达式，判断表达式是否成立，即特殊参数值是否合法</li>
<li>如果以上都为真，说明特殊参数合法，最后则使用 delete 从参数中删除特殊参数</li>
</ul>

<p><strong>2. 普通参数处理</strong></p>

<p>处理完毕特殊参数后，就需要处理普通参数。普通参数只需要满足：</p>

<ul>
<li>不为 <code>undefined</code></li>
<li>不为 <code>null</code></li>
<li>不为空字符串 <code>''</code></li>
<li>不为空数组 <code>[]</code></li>
</ul>

<p>普通参数，即 <code>req.body</code> <code>req.params</code> <code>req.query</code> 中的所有非特殊参数。</p>

<p>除了在判断特殊参数的时候，需要 delete 特殊参数，还需要 <code>delete req.params['0']</code>。因为对于 <code>req.params</code>，当 URL 没有 <code>path</code> 部分的时候，<code>req.params['0']</code> 为 <code>''</code>。</p>

<p>处理完毕之后，若所有参数都合法，则返回 <code>next()</code>。</p>

<h4 id="模块使用">模块使用</h4>

<p><strong>1. 创建没有挂载路径的中间件</strong></p>

<p>首先在 Express 项目入口文件，创建一个参数处理中间件：</p>

<pre><code>// app.js
const checkParameters = require('./checkParameters');
// ...
app.use(function(req, res, next) {
  checkParameters(req, res, next);
});
</code></pre>

<p>注意这里的 <code>app.use()</code> 需要放在创建具体路由中间件如<code>app.use('/', index)</code> 之前。</p>

<p>当 Express 接收到 HTTP 请求后，首先该中间件会检测参数是否合法（只判断是否为 null 或 undefine 或 &ldquo; 或 []）。如果不合法，则直接返回；如果合法，则程序继续执行，由匹配该请求路径的路由继续完成该请求。</p>

<p><strong>2. 创建挂载路径的中间件</strong></p>

<p>如果需要对某个路由的特殊参数做处理，则需要创建一个挂载路径的中间件。</p>

<p>比如为们要判断 <code>/index</code> 路由的 GET 请求中的 <code>id</code> 参数，其中 <code>id</code> 的取值范围是 1-100，URL 形式如 <code>http://localhost:3000/index?id=10</code>，则可以这样使用该模块：</p>

<pre><code>// index.js
const checkParameters = require('./checkParameters');

// 挂载路径的中间件
router.get('/index', function(req, res, next) {
  const special = {
    eval: 'parseInt(req.query.id) &gt;= 1 &amp;&amp; parseInt(req.query.id) &lt;= 100',
    key: 'id',
    type: 'query'
  };
  checkParameters(req, res, next, special);
});
// 正常逻辑处理
router.get('/index', function(req, res, next) {
  // 正常逻辑
});
</code></pre>

<p>当 Express 接收到一个 HTTP 请求的时候，首先还是没有挂载路径的中间件先处理请求，检测参数；然后该挂载路径的中间件再检测参数；最后才执行正常处理逻辑。</p>

<hr />

<p>Github Issue: <a href="https://github.com/nodejh/nodejh.github.io/issues/5">https://github.com/nodejh/nodejh.github.io/issues/5</a></p>
                </section>
            </article>

            

            
                <div id="disqus_thread"></div>
<script type="text/javascript">
    var disqus_shortname = 'nodejh'; 

     
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</div>

            

            
                <ul id="post-list" class="archive readmore">
    <h3>Read more</h3>
    
    
        
        <li>
            <a href="http://nodejh.com/post/how-to-vertically-middle-align-floated-elements-with-flexbox/">使用 Flexbox 使浮动元素垂直居中<aside class="dates">Jul 28</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/css-responsive-navigation-menu/">纯 CSS 实现响应式导航菜单<aside class="dates">Jul 28</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/vue2-tutorials-02-todolist/">实现一个TodoList - Vue2 Tutorials (二)<aside class="dates">Jul 17</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/electron-quick-start/">Electron 快速入门<aside class="dates">Jul 6</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/vue2-tutorials-01-quick-start/">快速入门 - Vue2 Tutorials (一)<aside class="dates">Jul 6</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/project-documentation-with-hexo-static-site-generator/">使用 Hexo 创建项目文档网站<aside class="dates">Jul 5</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/handy-mysql-commands/">常用 MySQL 命令<aside class="dates">Mar 30</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/understand-the-oracle-startup-process/">深入理解 Oracle 启动原理<aside class="dates">Mar 26</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/setting-environmental-variables-in-macos/">macOS/Linux 环境变量设置<aside class="dates">Mar 19</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/how-to-create-auto-increment-column-in-oracle/">在 Oracle 中设置自增列<aside class="dates">Feb 20</aside></a>
        </li>
        
   
</ul>
            

            <footer id="footer">
    
        <div id="social">

	
	
    <a class="symbol" href="https://www.facebook.com/nodejh">
        <i class="fa fa-facebook-square"></i>
    </a>
    
    <a class="symbol" href="https://www.github.com/nodejh">
        <i class="fa fa-github-square"></i>
    </a>
    
    <a class="symbol" href="https://www.twitter.com/nodejh">
        <i class="fa fa-twitter-square"></i>
    </a>
    


</div>

    
    <p class="small">
    
       © Copyright 2017 <i class="fa fa-heart" aria-hidden="true"></i> nodejh
    
    </p>
    <p class="small">
        Powered by <a href="http://www.gohugo.io/">Hugo</a> Theme By <a href="https://github.com/nodejh/hugo-theme-cactus-plus">nodejh</a>
    </p>
</footer>

        </section>

        <script src="http://nodejh.com/js/jquery-2.2.4.min.js"></script>
<script src="http://nodejh.com/js/main.js"></script>
<script src="http://nodejh.com/js/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>




  
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-84989670-1', 'auto');
ga('send', 'pageview');
</script>




<script>
var baiduAnalytics = '39843ea392201290bd6f76173d2e1633';
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?" + baiduAnalytics;
  var s = document.getElementsByTagName("script")[0];
  s.parentNode.insertBefore(hm, s);
})();
</script>


    </body>
</html>
